package echiquier;

import java.util.HashMap;
import java.util.HashSet;

import piece.Piece;
import piece.PieceType;
import piece.Pion;
import core.AnnulationException;
import core.Cardinalite;
import core.Coordonnees;

/**
 * Cette classe contient uniquement des méthodes utilitaires pour la partie d'échec
 * @author baptiste.andrini
 */
public class Echiquier_deplacement_Utils {

	/**
	 * Constructeur privé pour éviter d'instancier la classe
	 */
	private Echiquier_deplacement_Utils(){}
	
	/**
	 * Fonction permettant de déterminer pour une pièce donnée l'ensembles des cases atteignables en fonction du plateau
	 * @param piece
	 * @param plateau
	 * @return ensemble des cases atteignables
	 */
	public static HashSet<Case> peutAtteindre(Piece piece, HashMap<Coordonnees, Case> plateau) {
		
		HashSet<Case> casesAtteignables = new HashSet<Case>();
		
		//Si la pièce est nulle ou est éliminé ou si le plateau est nulle ou vide, on retourne l'ensemble vide
		if (piece == null || plateau == null || piece.estElimine() || plateau.isEmpty()) {
			return casesAtteignables;
			
		//Si la pièce est un roi
		} else if (piece.getType() == PieceType.Roi) {
			Case caseVoisine;
			//pour toutes les cases voisines
			for (Cardinalite c : Cardinalite.values()) {
				caseVoisine = getVoisin(piece.getCase(), c,plateau);
				
				//on les rajoutes si la case existe est que la cible ne contient pas un allié
				if (caseVoisine != null && (caseVoisine.isFree() || caseVoisine.getPiece().getCouleur() != piece.getCouleur())) {
					casesAtteignables.add(caseVoisine);
				}
			}
			
		//si la pièce est une reine
		} else if (piece.getType() == PieceType.Reine) {
			//on rajoute toute les lignes de chaque cardinalité
			for (Cardinalite c : Cardinalite.values()) {
				casesAtteignables.addAll(getLignePossible(piece,c, plateau, false));
			}
			
		//si la pièce est un fou
		} else if(piece.getType() == PieceType.Fou) {
			//on rajoute toutes les lignes de cardinalités diagonales
			for (Cardinalite c : Cardinalite.getCardinalitesDiagonales()) {
				casesAtteignables.addAll(getLignePossible(piece, c, plateau, false));
			}
			
		//si la pièce est un cavalier
		} else if(piece.getType() == PieceType.Cavalier) {
			//pour chaque cardinalité longitudinale
			Case caseCavalier;
			//pour chaque coordonnées longitudinale
			for (Cardinalite c : Cardinalite.getCardinalitesLongitudinales()) {
				//Pour chaque coordonnées perpendiculaires à ces premières
				for (Cardinalite perpendicular : Cardinalite.getPerpendiculars(c)) {
					//on prend la voisine perpendiculaire à la case deux case après le cavalier dans la direction longitudinale donnée
					caseCavalier = getVoisin(getCase(piece.getCase(), 2, c, plateau), perpendicular, plateau);
					//si cette case n'est pas nulle ou ne contient pas de pièce alliée on la rajoute aux cases atteignables
					if (caseCavalier != null && (caseCavalier.isFree() || caseCavalier.getPiece().getCouleur() != piece.getCouleur())) {
						casesAtteignables.add(caseCavalier);
					}
				}
			}
			
		//si la pièce est une tour
		} else if(piece.getType() == PieceType.Tour){
			//pour chaque cardinalité longitudinale
			for (Cardinalite c : Cardinalite.getCardinalitesLongitudinales()) {
				//on ajoute la ligne en direction de cette cardinalité
				casesAtteignables.addAll(getLignePossible(piece, c, plateau, false));
			}
		
		//si la pièce est un pion
		} else if(piece.getType() == PieceType.Pion) {
			Case caseVoisine;
			for (Cardinalite c : Cardinalite.getAttaquePion(piece.getCouleur())) {
				caseVoisine = getVoisin(piece.getCase(), c, plateau);
				if (caseVoisine != null && !caseVoisine.isFree() && caseVoisine.getPiece().getCouleur() != piece.getCouleur()) {
					casesAtteignables.add(caseVoisine);
				} else if (caseVoisine != null) {
					System.out.println(caseVoisine);
					System.out.println("Free : " + caseVoisine.isFree());
					System.out.println("Couleur pion : " + piece.getCouleur());
					if (!caseVoisine.isFree()) {
						System.out.println("Couleur autre pièce : " + caseVoisine.getPiece().getCouleur());
					}
				}
			}
			caseVoisine = getVoisin(piece.getCase(), Cardinalite.getDirectionPion(piece.getCouleur()), plateau);
			if (caseVoisine != null && caseVoisine.isFree()) {
				casesAtteignables.add(caseVoisine);
				
				if (!((Pion)piece).sestDeplace()) {
					caseVoisine = getVoisin(caseVoisine, Cardinalite.getDirectionPion(piece.getCouleur()), plateau);
					if (caseVoisine != null && caseVoisine.isFree()) {
						casesAtteignables.add(caseVoisine);
					}
				}
			}
		}
		
		return casesAtteignables;
	}
	
	/**
	 * Fonction permettant de déterminer pour une pièce donnée l'ensemble des case quelle menace directement en fonction du plateau
	 * @param piece
	 * @param plateau
	 * @return
	 * @throws NullPointerException
	 */
	public static HashSet<Case> getZoneMenace(Piece piece, HashMap<Coordonnees, Case> plateau) {
		
		HashSet<Case> casesMenacees = new HashSet<Case>();
		
		//Si la pièce est nulle ou est éliminé ou si le plateau est nulle ou vide, on retourne l'ensemble vide
		if (piece == null || plateau == null || piece.estElimine() || plateau.isEmpty()) {
			return casesMenacees;
			
		//Si la pièce est un roi
		} else if (piece.getType() == PieceType.Roi) {
			Case caseVoisine;
			//pour toutes les cases voisines
			for (Cardinalite c : Cardinalite.values()) {
				caseVoisine = getVoisin(piece.getCase(), c,plateau);
				
				//on les rajoutes si la case existe est que la cible ne contient pas un allié
				if (caseVoisine != null) {
					casesMenacees.add(caseVoisine);
				}
			}
			
		//si la pièce est une reine
		} else if (piece.getType() == PieceType.Reine) {
			//on rajoute toute les lignes de chaque cardinalité
			for (Cardinalite c : Cardinalite.values()) {
				casesMenacees.addAll(getLignePossible(piece,c, plateau, true));
			}
			
		//si la pièce est un fou
		} else if(piece.getType() == PieceType.Fou) {
			//on rajoute toutes les lignes de cardinalités diagonales
			for (Cardinalite c : Cardinalite.getCardinalitesDiagonales()) {
				casesMenacees.addAll(getLignePossible(piece, c, plateau, true));
			}
			
		//si la pièce est un cavalier
		} else if(piece.getType() == PieceType.Cavalier) {
			//pour chaque cardinalité longitudinale
			Case caseCavalier;
			//pour chaque coordonnées longitudinale
			for (Cardinalite c : Cardinalite.getCardinalitesLongitudinales()) {
				//Pour chaque coordonnées perpendiculaires à ces premières
				for (Cardinalite perpendicular : Cardinalite.getPerpendiculars(c)) {
					//on prend la voisine perpendiculaire à la case deux case après le cavalier dans la direction longitudinale donnée
					caseCavalier = getVoisin(getCase(piece.getCase(), 2, c, plateau), perpendicular, plateau);
					//si cette case n'est pas nulle ou ne contient pas de pièce alliée on la rajoute aux cases atteignables
					if (caseCavalier != null) {
						casesMenacees.add(caseCavalier);
					}
				}
			}
			
		//si la pièce est une tour
		} else if(piece.getType() == PieceType.Tour){
			//pour chaque cardinalité longitudinale
			for (Cardinalite c : Cardinalite.getCardinalitesLongitudinales()) {
				//on ajoute la ligne en direction de cette cardinalité
				casesMenacees.addAll(getLignePossible(piece, c, plateau, true));
			}
		
		//si la pièce est un pion
		} else if(piece.getType() == PieceType.Pion) {
			Case caseVoisine;
			for (Cardinalite c : Cardinalite.getAttaquePion(piece.getCouleur())) {
				caseVoisine = getVoisin(piece.getCase(), c, plateau);
				if (caseVoisine != null) {
					casesMenacees.add(caseVoisine);
				}
			}
		}
		
		return casesMenacees;
	}
	
	/**
	 * Fonction pour déterminer la zone de menace d'un jeu entier
	 * @param jeu
	 * @param plateau
	 * @return Ensemble des cases menacés par le jeu
	 */
	public static HashSet<Case> getZoneMenace(Jeu jeu, HashMap<Coordonnees, Case> plateau) {
		HashSet<Case> zoneMenace = new HashSet<Case>();
		
		//si le jeu et le plateau ne sont pas nuls
		if (jeu != null && plateau != null) {
			//pour chaque pièce dans le jeu
			for (Piece p : jeu.getPieces()) {
				//on ajoute sa zone de menace
				zoneMenace.addAll(getZoneMenace(p, plateau));
			}
		}
		
		return zoneMenace;
	}
	
	/**
	 * Cette méthode retourne pour une case et une cardinalité données la case voisine. Si aucune case n'est accessible, elle retourne null.
	 * @param ca La case dont on veut la voisine.
	 * @param card La direction pour regarder si il y a une voisine.
	 * @param plateau Le plateau de jeu.
	 * @return La case voisine, ou null si il n'en existe pas (bord du plateau).
	 */
	public static Case getVoisin(Case ca, Cardinalite card,HashMap<Coordonnees, Case> plateau) {
		if (ca == null || plateau == null) {
			return null;
		} else {
			return plateau.get(ca.getCoordonnees().getVoisin(card));
		}
	}
	
	/**
	 * fonction retournant la case de distance donnée à une cardinalité donnée par rapport à une case donnée
	 * @param caseDep	case de départ
	 * @param distance			distance en nombre de case
	 * @param card		cardinalité
	 * @param plateau	plateau de jeu
	 * @return			case 
	 */
	public static Case getCase(Case caseDep, int distance, Cardinalite card, HashMap<Coordonnees, Case> plateau) {
		Case caseCourrante = caseDep;
		
		if (caseDep == null || plateau == null) {
			return null;
		} else {
			for (int i = 0; i < distance && caseCourrante != null; i++) {
				caseCourrante = getVoisin(caseCourrante, card, plateau);
			}
		}
		
		return caseCourrante;
	}
	
	/**
	 * Cette méthode retourne la case contenu après le déplacement donnée depuis la case donnée
	 * @param caseDep		case de départ
	 * @param deplacement	déplacement voulu
	 * @param plateau		plateau d'échec
	 * @return case donnée par la somme des coordonnées données et celle de la case donnée
	 */
	public static Case getCase(Case caseDep, Coordonnees deplacement, HashMap<Coordonnees, Case> plateau) {
		
		//si une des données est nulle, on retourne nulle
		if (caseDep != null && deplacement != null && plateau != null) {
			return plateau.get(new Coordonnees(caseDep.getCoordonnees().x+deplacement.x, caseDep.getCoordonnees().y + deplacement.y));
		} else {
			return null;
		}
		
	}
	
	/**
	 * Cette méthode parcours toute une ligne pour une case de départ donnée et une cardinalité. Elle retourne une collection de case possible de se déplacer. Dans le cas où on tombe sur une case avec un ennemi, elle sera ajouté et on ira pas plus loin dans la ligne.
	 * @param p pièce de départ.
	 * @param card La direction dans laquelle on veut aller.
	 * @param plateau L'ensemble du plateau.
	 * @return Retourne la collection de case où on peut se déplacer.
	 */
	public static HashSet<Case> getLignePossible(Piece p, Cardinalite card, HashMap<Coordonnees, Case> plateau, boolean compteAllie){
		HashSet<Case> ligne = new HashSet<Case>();
		
		Case caseCourrante = getVoisin(p.getCase(), card, plateau);
		boolean pieceRencontree = false;
		
		while (caseCourrante != null && !pieceRencontree) {
			if (caseCourrante.isFree()){
				ligne.add(caseCourrante);
				caseCourrante = getVoisin(caseCourrante, card, plateau);
			} else if (compteAllie || (!caseCourrante.isFree() && caseCourrante.getPiece().getCouleur() != p.getCouleur())) {
				ligne.add(caseCourrante);
				pieceRencontree = true;
			} else if (compteAllie || (!caseCourrante.isFree() && caseCourrante.getPiece().getCouleur() == p.getCouleur())) {
				pieceRencontree = true;
			}
			
		}
		
		return ligne;
	}
	
	/**
	 * Méthode renvoyant l'ensemble des pièces d'un jeu déplacable
	 * @param jeu
	 * @param plateau
	 * @return
	 * @throws RoiNonTrouvableException 
	 * @throws AnnulationException 
	 */
	public static HashSet<Piece> getPiecesDeplacable(Echiquier echiquier) {
		HashSet<Piece> hs = new HashSet<Piece>();
		
		HashSet<Piece> pieces = echiquier.getJeuCourant().getPieces();
		for (Piece p : pieces) {
			if (!getDeplacementValide(p, echiquier).isEmpty()) {
				hs.add(p);
			}
		}
		
		return hs;
	}

	/**
	 * Accesseurs des cases à déplacements valides 
	 * @param piece
	 * @param echiquier
	 * @return
	 * @throws AnnulationException
	 * @throws RoiNonTrouvableException
	 */
	public static HashSet<Case> getDeplacementValide(Piece piece, Echiquier echiquier) {
		HashSet<Case> hsRes = new HashSet<Case>();
		
		//on récupère les cases atteignables théoriquement par la pièce
		HashSet<Case> casesAtteignables = peutAtteindre(piece, echiquier.getPlateau());
		
		System.out.println(casesAtteignables);
		
		//pour chaque case atteignable
		for (Case c : casesAtteignables) {
			//on créé un objet déplacement vers cette case
			Deplacement dep = new Deplacement(piece, c.getCoordonnees());
			
			// si le déplacement vers cette case est valide on le rajoute à l'ensemble des cases avec un déplacement valide
			if (dep.estValide(echiquier)) {
				hsRes.add(c);
			}
		}
		
		return hsRes;
	}
	
	/**
	 * Accesseurs des cases à déplacements valides du jeu courant de l'échiquier donné
	 * @param echiquier
	 * @return
	 * @throws AnnulationException
	 * @throws RoiNonTrouvableException
	 */
	public static HashSet<Case> getDeplacementsValides(Echiquier echiquier) {
		HashSet<Case> hs = new HashSet<Case>();
		
		//pour toute les pièces du jeu courant
		for (Piece piece : echiquier.getJeuCourant().getPieces()) {
			//on récupère les case à déplacement valides de la pièce
			HashSet<Case> deplacementValidePiece = getDeplacementValide(piece, echiquier);
			
			hs.addAll(deplacementValidePiece);
		}
		
		return hs;
	}
}
